Skip to content

perf: force custom plans for the non-inlined retrieval RPCs (round 2, phase 6)#484

Merged
BigSimmo merged 1 commit into
claude/perf-r2-payload-trimfrom
claude/perf-r2-plan-cache-migration
Jul 11, 2026
Merged

perf: force custom plans for the non-inlined retrieval RPCs (round 2, phase 6)#484
BigSimmo merged 1 commit into
claude/perf-r2-payload-trimfrom
claude/perf-r2-plan-cache-migration

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

Summary

Phase 6 of the round-2 performance initiative (stacked on #482). Implements scale-readiness F1 — the single largest measured cold-answer latency win of the round.

The retrieval RPCs are LANGUAGE sql/plpgsql with SET search_path, making them non-inlinable: their bodies get planned with unknown parameters, and once a pooled PostgREST connection caches the generic plan, every call pays near-seq-scan strategies. match_document_table_facts_text measured 5.3 s per call in that mode (vs ~1.1 s custom-plan), and the answer path fans it out up to 3× in parallel per cold request.

plan_cache_mode = force_custom_plan applied to the four profiled victims: table-facts text, memory-cards hybrid, index-units hybrid, embedding-fields hybrid. Results-identical by construction — only the plan changes, never which rows qualify — so no eval gate applies (per the F1 write-up in docs/scale-readiness-review.md).

Live application (done, user-approved)

Applied to Clinical KB Database (sjrfecxgysukkwxsowpy) on 2026-07-11 via the Supabase connector:

  • Settings verified in proconfig on all four functions
  • Before/after harness (8 iterations per function, same query + stored embedding): identical row counts, steady-state timings unchanged (~1132 / ~10 / ~494 / ~13 ms) — no regression, and the cached-generic-plan mode is now unreachable
  • The in-session harness cannot reproduce the pooled-connection pathology directly (Postgres runs custom plans for a session's first five calls); the 5.3 s → 1.1 s effect on pooled connections is established by F1's live profiling

The migration codifies the setting for other environments; ALTER … SET is idempotent, and it is body-agnostic so it does not collide with the known schema.sql↔live body drift on this RPC.

Deliberately not included

  • statement_timeout on retrieval functions (was in the round-2 plan): a function-level SET cannot re-arm the timer for the statement already in flight, and a role-level timeout would endanger the minutes-long ingestion RPCs on the same service role. Client-disconnect cancellation is handled app-side via .abortSignal() (perf: remove serial dead weight from the answer hot path (round 2, phase 3) #480). Rationale recorded in the migration and in the F1 status note.
  • The F1 "full fix" (plpgsql + dynamic EXECUTE, recovers to ~70 ms): still blocked on the schema.sql body-drift reconciliation; remains open in the doc.

Also updated

  • docs/scale-readiness-review.md: F1 status block marking the mitigation applied + measured verification

🤖 Generated with Claude Code

Round-2 phase 6 (scale-readiness F1). These LANGUAGE sql/plpgsql functions
carry SET search_path, so they are non-inlinable and their bodies get planned
with unknown parameters; a pooled connection that caches the generic plan pays
near-seq-scan strategies on every call (match_document_table_facts_text
measured 5.3 s per call live, ~1.1 s with a custom plan, fanned out up to 3x
in parallel per cold answer).

plan_cache_mode = force_custom_plan on the four profiled victims:
table-facts text, memory-cards hybrid, index-units hybrid, embedding-fields
hybrid. Results-identical by construction (only the plan changes, never which
rows qualify) — no eval gate.

Applied to the live Clinical KB Database (sjrfecxgysukkwxsowpy) 2026-07-11
with before/after verification: settings visible in proconfig, identical row
counts, steady-state timings unchanged (~1132/10/494/13 ms), and the cached
generic-plan mode now unreachable. This migration codifies the setting for
other environments; ALTER ... SET is idempotent.

statement_timeout was deliberately dropped from this phase: a function-level
SET cannot re-arm the running statement's timer, and a role-level timeout
would endanger the minutes-long ingestion RPCs on the same service role.
Client-disconnect cancellation is handled app-side via .abortSignal() (#480).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 151b2b79-987e-4205-8034-928937aece42

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/perf-r2-plan-cache-migration
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/perf-r2-plan-cache-migration

Comment @coderabbitai help to get the list of available commands.

@supabase

supabase Bot commented Jul 11, 2026

Copy link
Copy Markdown

Updates to Preview Branch (claude/perf-r2-plan-cache-migration) ↗︎

Deployments Status Updated
Database Sat, 11 Jul 2026 03:56:22 UTC
Services Sat, 11 Jul 2026 03:56:22 UTC
APIs Sat, 11 Jul 2026 03:56:22 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Sat, 11 Jul 2026 03:56:29 UTC
Migrations Sat, 11 Jul 2026 03:57:20 UTC
Seeding Sat, 11 Jul 2026 03:57:23 UTC
Edge Functions Sat, 11 Jul 2026 03:57:24 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5f36914c04

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

set plan_cache_mode = 'force_custom_plan';

alter function public.match_document_memory_cards_hybrid(vector, text, integer, double precision, uuid[], uuid)
set plan_cache_mode = 'force_custom_plan';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mirror function GUCs in drift state

This migration changes the canonical function definitions via proconfig, but supabase/schema.sql and supabase/drift-manifest.json are not updated in the same change. The drift snapshot hashes pg_get_functiondef(...), and the allowlist currently covers only match_document_table_facts_text, so after this is applied (the doc says it already was on live) npm run check:drift will report unexpected mismatches for the three hybrid RPCs and mask future real drift. Please mirror these SET plan_cache_mode clauses into the schema/manifest path or explicitly account for the new drift.

Useful? React with 👍 / 👎.

alter function public.match_document_table_facts_text(text, integer, uuid[], uuid)
set plan_cache_mode = 'force_custom_plan';

alter function public.match_document_memory_cards_hybrid(vector, text, integer, double precision, uuid[], uuid)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Qualify pgvector signatures in the migration

This file does not set search_path, so in any migration/SQL execution context where extensions is not already on the path, PostgreSQL cannot resolve the unqualified vector type and the migration aborts before applying the plan-cache settings. The repo installs pgvector in the extensions schema and most signature checks use extensions.vector; qualify these three ALTER FUNCTION signatures (or set search_path = public, extensions) to make replay independent of the caller's session state.

Useful? React with 👍 / 👎.

@BigSimmo
BigSimmo merged commit 5a6ce71 into claude/perf-r2-payload-trim Jul 11, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant